/*
 * Opus音频播放器（修正版）
 * 播放由 record.c 生成的带 4 字节帧头的自定义 Opus 文件
 */

#include <stdio.h>
#include <stdlib.h>
#include <alsa/asoundlib.h>   // ALSA 音频库
#include <opus/opus.h>        // Opus 编解码库
#include <stdint.h>           // uint32_t
#include <inttypes.h>         // PRIu32

#define IFILE "record.opus"   // 输入文件名

// 播放器结构体
struct player {
    snd_pcm_t* device;
    int channels;
    int sample_rate;
    int duration;             // 周期时长（毫秒）
    int frames;               // 每周期帧数
    OpusDecoder* decoder;
    opus_int16* buffer;
};

// 创建播放器
struct player* player_create(const char* name, int sample_rate, int channels, int duration)
{
    struct player* player = malloc(sizeof(struct player));
    if (!player) return NULL;

    player->channels = channels;
    player->sample_rate = sample_rate;
    player->duration = duration;

    int error = snd_pcm_open(&player->device, name, SND_PCM_STREAM_PLAYBACK, 0);
    if (error < 0) {
        fprintf(stderr, "打开设备 %s 失败: %s\n", name, snd_strerror(error));
        free(player);
        return NULL;
    }

    error = snd_pcm_set_params(player->device,
                               SND_PCM_FORMAT_S16_LE,
                               SND_PCM_ACCESS_RW_INTERLEAVED,
                               player->channels,
                               player->sample_rate,
                               1,
                               player->duration * 1000);
    if (error < 0) {
        fprintf(stderr, "设置PCM参数失败: %s\n", snd_strerror(error));
        snd_pcm_close(player->device);
        free(player);
        return NULL;
    }

    player->decoder = opus_decoder_create(player->sample_rate, player->channels, &error);
    if (error != OPUS_OK) {
        fprintf(stderr, "创建Opus解码器失败: %s\n", opus_strerror(error));
        snd_pcm_close(player->device);
        free(player);
        return NULL;
    }

    player->frames = player->sample_rate * player->duration / 1000;
    player->buffer = malloc(snd_pcm_frames_to_bytes(player->device, player->frames));
    if (!player->buffer) {
        opus_decoder_destroy(player->decoder);
        snd_pcm_close(player->device);
        free(player);
        return NULL;
    }

    return player;
}

// 销毁播放器
void player_destroy(struct player *player)
{
    if (!player) return;
    free(player->buffer);
    snd_pcm_close(player->device);
    opus_decoder_destroy(player->decoder);
    free(player);
}

// 启动播放
int player_start(struct player *player)
{
    return player ? snd_pcm_start(player->device) : -1;
}

// 停止播放
int player_stop(struct player *player)
{
    return player ? snd_pcm_drain(player->device) : -1;
}

// 播放一帧数据
int play_audio(struct player* player, char* data, size_t size)
{
    if (!player) return -1;

    int dec_frames = opus_decode(player->decoder, (unsigned char*)data, (opus_int32)size, player->buffer, player->frames, 0);
    if (dec_frames < 0) {
        fprintf(stderr, "Opus解码失败: %s\n", opus_strerror(dec_frames));
        return -1;
    }

    snd_pcm_sframes_t frames = snd_pcm_writei(player->device, player->buffer, dec_frames);
    if (frames < 0) {
        fprintf(stderr, "PCM写入失败: %s\n", snd_strerror(frames));
        return -1;
    }

    return 0;
}

// 全局文件缓冲区
char file_buffer[4096];

int main()
{
    FILE *fp = fopen(IFILE, "rb");
    if (!fp) {
        perror(IFILE);
        return EXIT_FAILURE;
    }

    struct player* player = player_create("default", 16000, 1, 20);
    if (!player) {
        fclose(fp);
        return EXIT_FAILURE;
    }

    player_start(player);

    while (1) {
        uint32_t rbytes;  // 帧数据长度（无符号）

        // 尝试读取4字节帧头
        if (fread(&rbytes, 1, sizeof(rbytes), fp) != sizeof(rbytes)) {
            if (feof(fp)) {
                puts("播放完成");
            } else {
                perror("读取帧头失败");
            }
            break;
        }

        // 检查是否超出缓冲区
        if (rbytes > sizeof(file_buffer)) {
            fprintf(stderr, "帧大小 %" PRIu32 " 超过缓冲区容量 %zu\n", rbytes, sizeof(file_buffer));
            break;
        }

        // 读取实际帧数据
        size_t bytes = fread(file_buffer, 1, rbytes, fp);
        if (bytes != rbytes) {
            fprintf(stderr, "期望读取 %" PRIu32 " 字节，实际读取 %zu 字节\n", rbytes, bytes);
            break;
        }

        printf("读取 %zu 字节\n", bytes);

        if (play_audio(player, file_buffer, bytes) < 0) {
            // 可选择继续或退出
            continue;
        }
    }

    player_stop(player);
    player_destroy(player);
    fclose(fp);

    return EXIT_SUCCESS;
}
